<template>
	<view v-if="!disabled" class="tn-image-upload-class tn-image-upload">
		<block v-if="showUploadList">
			<view v-for="(item, index) in lists" :key="index"
				class="tn-image-upload__item tn-image-upload__item-preview" :style="{
          width: $t.string.getLengthUnitValue(width),
          height: $t.string.getLengthUnitValue(height)
        }">
				<!-- 删除按钮 -->
				<view v-if="deleteable" class="tn-image-upload__item-preview__delete" @tap.stop="deleteItem(index)"
					:style="{
            borderTopColor: deleteBackgroundColor
          }">
					<view class="tn-image-upload__item-preview__delete--icon" :class="[`tn-icon-${deleteIcon}`]" :style="{
              color: deleteColor
            }"></view>
				</view>
				<!-- 进度条 -->
				<tn-line-progress v-if="showProgress && item.progress > 0 && !item.error"
					class="tn-image-upload__item-preview__progress" :percent="item.progress" :showPercent="false"
					:round="false" :height="8"></tn-line-progress>
				<!-- 重试按钮 -->
				<view v-if="item.error" class="tn-image-upload__item-preview__error-btn" @tap.stop="retry(index)">点击重试
				</view>
				<!-- 图片信息 -->
				<image class="tn-image-upload__item-preview__image" :src="item.url || item.path" :mode="imageMode"
					@tap.stop="doPreviewImage(item.url || item.path, index)"></image>
			</view>
		</block>
		<!-- <view v-if="$slots.file || $slots.$file" style="width: 100%;">
      
    </view> -->
		<!-- 自定义图片展示列表 -->
		<slot name="file" :file="lists"></slot>

		<!-- 添加按钮 -->
		<view v-if="maxCount > lists.length" class="tn-image-upload__add"
			:class="{'tn-image-upload__add--custom': customBtn}" @tap="selectFile">
			<!-- 添加按钮 -->
			<view v-if="!customBtn" class="tn-image-upload__item tn-image-upload__item-add" hover-class="tn-hover-class"
				hover-stay-time="150" :style="{
          width: $t.string.getLengthUnitValue(width),
          height: $t.string.getLengthUnitValue(height)
        }">
				<view class="tn-image-upload__item-add--icon tn-icon-add"></view>
				<view class="tn-image-upload__item-add__tips" v-if="uploadText">{{ uploadText }}</view>
			</view>
			<!-- 自定义添加按钮 -->
			<view>
				<slot name="addBtn"></slot>
			</view>
		</view>
	</view>
</template>



<script>
	import Compressor from 'compressorjs';
	export default {
		name: 'tn-image-upload',
		props: {
			// 已上传的文件列表
			fileList: {
				type: Array,
				default () {
					return []
				}
			},
			// 上传图片地址
			action: {
				type: String,
				default: ''
			},
			// 上传文件的字段名称
			name: {
				type: String,
				default: 'file'
			},
			// 头部信息
			header: {
				type: Object,
				default () {
					return {}
				}
			},
			// 携带的参数
			formData: {
				type: Object,
				default () {
					return {}
				}
			},
			// 是否禁用
			disabled: {
				type: Boolean,
				default: false
			},
			// 是否自动上传
			autoUpload: {
				type: Boolean,
				default: true
			},
			// 最大上传数量
			maxCount: {
				type: Number,
				default: 9
			},
			// 是否显示组件自带的图片预览
			showUploadList: {
				type: Boolean,
				default: true
			},
			// 预览上传图片的裁剪模式
			imageMode: {
				type: String,
				default: 'aspectFill'
			},
			// 点击图片是否全屏预览
			previewFullImage: {
				type: Boolean,
				default: true
			},
			// 是否显示进度条
			showProgress: {
				type: Boolean,
				default: true
			},
			// 是否显示删除按钮
			deleteable: {
				type: Boolean,
				default: true
			},
			// 删除按钮图标
			deleteIcon: {
				type: String,
				default: 'close'
			},
			// 删除按钮的背景颜色
			deleteBackgroundColor: {
				type: String,
				default: ''
			},
			// 删除按钮的颜色
			deleteColor: {
				type: String,
				default: ''
			},
			// 上传区域提示文字
			uploadText: {
				type: String,
				default: '选择图片'
			},
			// 显示toast提示
			showTips: {
				type: Boolean,
				default: true
			},
			// 自定义选择图标按钮
			customBtn: {
				type: Boolean,
				default: false
			},
			// 预览图片和选择图片区域的宽度
			width: {
				type: Number,
				default: 80
			},
			// 预览图片和选择图片区域的高度
			height: {
				type: Number,
				default: 80
			},
			// 选择图片的尺寸
			// 参考上传文档 https://uniapp.dcloud.io/api/media/image
			sizeType: {
				type: Array,
				default () {
					return ['original', 'compressed']
				}
			},
			// 图片来源
			sourceType: {
				type: Array,
				default () {
					return ['album', 'camera']
				}
			},
			// 是否可以多选
			multiple: {
				type: Boolean,
				default: true
			},
			// 文件大小(byte)
			maxSize: {
				type: Number,
				default: 20 * 1024 * 1024
			},
			// 允许上传的类型
			limitType: {
				type: Array,
				default () {
					return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'image']
				}
			},
			// 是否自定转换为json
			toJson: {
				type: Boolean,
				default: true
			},
			// 上传前钩子函数，每个文件上传前都会执行
			beforeUpload: {
				type: Function,
				default: null
			},
			// 删除文件前钩子函数
			beforeRemove: {
				type: Function,
				default: null
			},
			index: {
				type: [Number, String],
				default: ''
			}
		},
		computed: {

		},
		data() {
			return {
				lists: [],
				uploading: false
			}
		},
		watch: {
			fileList: {
				handler(val) {
					val.map(value => {
						// 首先检查内部是否已经添加过这张图片，因为外部绑定了一个对象给fileList的话(对象引用)，进行修改外部fileList时，
						// 会触发watch，导致重新把原来的图片再次添加到this.lists
						// 数组的some方法意思是，只要数组元素有任意一个元素条件符合，就返回true，而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
						let tmp = this.lists.some(listVal => {
								return listVal.url === value.url
							})
							// 如果内部没有这张图片，则添加到内部
							!tmp && this.lists.push({
								url: value.url,
								error: false,
								progress: 100
							})
					})
				},
				immediate: true
			},
			lists(val) {
				this.$emit('on-list-change', val, this.index)
			}
		},
		methods: {
			// 清除列表
			clear() {
				this.lists = []
			},
			// 重新上传队列中上传失败所有文件
			reUpload() {
				this.uploadFile()
			},
			// 选择图片
			selectFile() {
				if (this.disabled) return
				const {
					name = '',
						maxCount,
						multiple,
						maxSize,
						sizeType,
						lists,
						camera,
						compressed,
						sourceType
				} = this
				let chooseFile = null
				const newMaxCount = maxCount - lists.length
				// 只选择图片的时候使用 chooseImage 来实现
				chooseFile = new Promise((resolve, reject) => {
					uni.chooseImage({
						count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
						sourceType,
						sizeType,
						success: resolve,
						fail: reject
					})
				})
				chooseFile.then(res => {
					let file = null
					let listOldLength = lists.length
					res.tempFiles.map((val, index) => {
						if (!this.checkFileExt(val)) return
						// 是否超出最大限制数量
						if (!multiple && index >= 1) return
						if (val.size > maxSize) {
							this.$emit('on-oversize', val, lists, this.index)
							this.showToast('超出可允许文件大小')
						} else {
							if (maxCount <= lists.length) {
								this.$emit('on-exceed', val, lists, this.index)
								this.showToast('超出最大允许的文件数')
								return
							}
							new Compressor(val, {
								quality: 0.5,
								width:500,
								height:500,
								success: (result) => {
									let mfile = this.blobToFile(result, result.name)
									let new_path = URL.createObjectURL(result);
									lists.push({
										url: new_path,
										progress: 0,
										error: false,
										file: mfile
									})

									this.$emit('on-choose-complete', this.lists, this.index)
									if (this.autoUpload) this.uploadFile(listOldLength)
								},
								error: (err) => {
									console.error(err); // 压缩失败的错误信息
									this.showToast('压缩图片出错了，请重试')
									this.$emit('on-choose-fail', '压缩图片出错了，请重试')
								}
							});
						}
					})

				}).catch(err => {
					this.$emit('on-choose-fail', err)
				})
			},

			blobToFile(blob, fileName) {
				// Blob对象本身包含了文件的数据，但是我们需要一个File对象来访问文件的元数据（如文件名）  
				// 创建一个新的File对象，它使用Blob对象作为内容，并附加文件名  
				return new File([blob], fileName, {
					type: blob.type,
					lastModified: Date.now() // 你可以设置为你需要的最后修改时间，这里使用当前时间  
				});
			},



			// 提示用户信息
			showToast(message, force = false) {
				if (this.showTips || force) {
					this.$t.message.toast(message)
				}
			},
			// 手动上传，通过ref进行调用
			upload() {
				this.uploadFile()
			},
			// 对失败图片进行再次上传
			retry(index) {
				this.lists[index].progress = 0
				this.lists[index].error = false
				this.lists[index].response = null
				this.$t.message.loading('重新上传')
				this.uploadFile(index)
			},
			// 上传文件
			async uploadFile(index = 0) {
				if (this.disabled) return
				if (this.uploading) return
				// 全部上传完成
				if (index >= this.lists.length) {
					this.$emit('on-uploaded', this.lists, this.index)
					return
				}
				// 检查是否已经全部上传或者上传中
				if (this.lists[index].progress === 100) {
					this.lists[index].uploadTask = null
					if (this.autoUpload) this.uploadFile(index + 1)
					return
				}
				// 执行before-upload钩子
				if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
					// 在微信，支付宝等环境(H5正常)，会导致父组件定义的函数体中的this变成子组件的this
					// 通过bind()方法，绑定父组件的this，让this的this为父组件的上下文
					// 因为upload组件可能会被嵌套在其他组件内，比如tn-form，这时this.$parent其实为tn-form的this，
					// 非页面的this，所以这里需要往上历遍，一直寻找到最顶端的$parent，这里用了this.$u.$parent.call(this)
					let beforeResponse = this.beforeUpload.bind(this.$t.$parent.call(this))(index, this.lists)
					// 判断是否返回了Promise
					if (!!beforeResponse && typeof beforeResponse.then === 'function') {
						await beforeResponse.then(res => {
							// promise返回成功，不进行操作继续
						}).catch(err => {
							// 进入catch回调的话，继续下一张
							return this.uploadFile(index + 1)
						})
					} else if (beforeResponse === false) {
						// 如果返回flase，继续下一张图片上传
						return this.uploadFile(index + 1)
					} else {
						// 为true的情况，不进行操作
					}
				}
				// 检查上传地址
				if (!this.action) {
					this.showToast('请配置上传地址', true)
					this.$emit('on-error', '请配置上传地址')
					return
				}

				//追加userid 和 token
				let tempData = {
					user_id: this.$storage.getStorageSync('userid'),
					token: this.$storage.getStorageSync('accessToken'),
					// #ifdef MP-WEIXIN
					platform: 1,
					// #endif
					...this.formData
				}

				this.lists[index].error = false
				this.uploading = true
				// 创建上传对象
				const task = uni.uploadFile({
					url: this.action,
					filePath: this.lists[index].url,
					name: this.name,
					formData: tempData,
					header: this.header,
					success: res => {
						// 判断啊是否为json字符串，将其转换为json格式
						let data = this.toJson && this.$t.test.jsonString(res.data) ? JSON.parse(res
							.data) : res.data
						if (![200, 201, 204].includes(res.statusCode)) {
							this.uploadError(index, data)
						} else {
							// console.log()
							this.lists[index].response = data.data.url
							this.lists[index].progress = 100
							this.lists[index].error = false
							if (data.ret === 200 && data.data.code == 0) {
								this.$emit('on-success', data.data, index, this.lists, this.index)
							} else {
								this.uploadError(index, data)
							}
							if (data.ret == 401) {
								this.$user.loginout()
							}
						}
					},
					fail: err => {
						this.uploadError(index, err)
					},
					complete: res => {
						this.$t.message.closeLoading()
						this.uploading = false
						this.uploadFile(index + 1)
						this.$emit('on-change', res, index, this.lists, this.index)
					}
				})
				this.lists[index].uploadTask = task
				task.onProgressUpdate(res => {
					if (res.progress > 0) {
						this.lists[index].progress = res.progress
						this.$emit('on-progress', res, index, this.lists, this.index)
					}
				})
			},
			// 上传失败
			uploadError(index, err) {
				this.lists[index].progress = 0
				this.lists[index].error = true
				this.lists[index].response = null
				this.showToast('上传失败，请重试')
				this.$emit('on-error', err, index, this.lists, this.index)
			},
			// 删除一个图片
			deleteItem(index) {
				if (!this.deleteable) return
				this.$t.message.modal(
					'提示',
					'您确定要删除吗？',
					async () => {
						// 先检查是否有定义before-remove移除前钩子
						// 执行before-remove钩子
						if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
							let beforeResponse = this.beforeRemove.bind(this.$t.$parent.call(this))(index, this
								.lists)
							// 判断是否返回promise 
							if (!!beforeResponse && typeof beforeResponse.then === 'function') {
								await beforeResponse.then(res => {
									// promise返回成功不进行操作
									this.handlerDeleteItem(index)
								}).catch(err => {
									this.showToast('删除操作被中断')
								})
							} else if (beforeResponse === false) {
								this.showToast('删除操作被中断')
							} else {
								this.handlerDeleteItem(index)
							}
						} else {
							this.handlerDeleteItem(index)
						}
					}, true)
			},
			// 移除文件操作
			handlerDeleteItem(index) {
				// 如果文件正在上传中，终止上传任务
				if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
					typeof this.lists[index].uploadTask !== 'undefined' && this.lists[index].uploadTask.abort()
				}
				this.lists.splice(index, 1)
				this.$forceUpdate()
				this.$emit('on-remove', index, this.lists, this.index)
				this.showToast('删除成功')
			},
			// 移除文件，通过ref手动形式进行调用
			remove(index) {
				if (!this.deleteable) return
				// 判断索引合法
				if (index >= 0 && index < this.lists.length) {
					this.lists.splice(index, 1)
				}
			},
			// 预览图片
			doPreviewImage(url, index) {
				if (!this.previewFullImage) return
				const images = this.lists.map(item => item.url || item.path)
				uni.previewImage({
					urls: images,
					current: url,
					success: () => {
						this.$emit('on-preview', url, this.lists, this.index)
					},
					fail: () => {
						this.showToast('预览图片失败')
					}
				})
			},
			// 检查文件后缀是否合法
			checkFileExt(file) {
				// 是否为合法后缀
				let noArrowExt = false
				// 后缀名
				let fileExt = ''
				const reg = /.+\./

				// #ifdef H5
				fileExt = file.name.replace(reg, '').toLowerCase()
				// #endif
				// #ifndef H5
				fileExt = file.path.replace(reg, '').toLowerCase()
				// #endif
				noArrowExt = this.limitType.some(ext => {
					return ext.toLowerCase() === fileExt
				})
				if (!noArrowExt) this.showToast(`不支持${fileExt}格式的文件`)
				return noArrowExt
			}
		}
	}
</script>

<style lang="scss" scoped>
	.tn-image-upload {
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		align-items: center;

		&__item {
			/* #ifndef APP-NVUE */
			display: flex;
			/* #endif */
			align-items: center;
			justify-content: center;
			width: 200rpx;
			height: 200rpx;
			overflow: hidden;
			margin: 12rpx;
			margin-left: 0;
			background-color: $tn-font-holder-color;
			position: relative;
			border-radius: 10rpx;

			&-preview {
				border: 1rpx solid $tn-border-solid-color;

				&__delete {
					display: flex;
					align-items: center;
					justify-content: center;
					position: absolute;
					top: 0;
					right: 0;
					z-index: 10;
					border-top: 60rpx solid;
					border-left: 60rpx solid transparent;
					border-top-color: $tn-color-red;
					width: 0rpx;
					height: 0rpx;

					&--icon {
						position: absolute;
						top: -50rpx;
						right: 6rpx;
						color: #FFFFFF;
						font-size: 24rpx;
						line-height: 1;
					}
				}

				&__progress {
					position: absolute;
					width: auto;
					bottom: 0rpx;
					left: 0rpx;
					right: 0rpx;
					z-index: 9;
					/* #ifdef MP-WEIXIN */
					display: inline-flex;
					/* #endif */
				}

				&__error-btn {
					position: absolute;
					bottom: 0;
					left: 0;
					right: 0;
					background-color: $tn-color-red;
					color: #FFFFFF;
					font-size: 20rpx;
					padding: 8rpx 0;
					text-align: center;
					z-index: 9;
					line-height: 1;
				}

				&__image {
					display: block;
					width: 100%;
					height: 100%;
					border-radius: 10rpx;
				}
			}

			&-add {
				flex-direction: column;
				color: $tn-content-color;
				font-size: 26rpx;

				&--icon {
					font-size: 40rpx;
				}

				&__tips {
					margin-top: 20rpx;
					line-height: 40rpx;
				}
			}
		}

		&__add {
			width: auto;
			display: inline-block;

			&--custom {
				width: 100%;
			}
		}
	}
</style>